package top.porchwood.util.db;

//import dfc.core.utils.db.bean.FieldProperty;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import top.porchwood.util.db.bean.FieldProperty;
//import top.porchwood.common.log.Logger;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.*;

/**
 * 处理对象的工具包
 *
 * @Author 一七年夏
 * @Link https://space.bilibili.com/16869954
 * @Email 2098833867@qq.com
 * @License GNU General Public License 3.0
 * @DateTime 2021-08-08 10:13
 */
public class ObjectUtils {
    //private static final Logger logger = Logger.getLogger();
    private static final Logger logger = LogManager.getLogger();

    /**
     * 将一个ResultSet实例化为一个对象，该方法对成员变量<b>忽略大小写</b>，请注意
     *
     * @param rs              输入的ResultSet对象，该方法不会使用 {@link ResultSet#next()}
     * @param type            输出对象类型
     * @param <T>             输出对象类型
     * @param ignoreException 是否忽略异常，但无法忽略构造对象时的异常
     * @return 输出对象
     * @throws RuntimeException 解析中出现了异常，详见Print出的错误信息
     */
    //* <ul>
    //* <li>NoSuchMethodException     找不到方法异常（缺少无形参构造函数）
    //* <li>InvocationTargetException 检查异常
    //* <li>SecurityException         安全性异常
    //* <li>InstantiationException    无法实例化对象异常
    //* <li>IllegalAccessException    权限不足异常（Private，应该不会出这个错误）
    //* <li>IllegalArgumentException  成员对象类型异常（实现接口 {@link ResultSetTypeConverter} 以自行处理，例如 {@link java.util.Calendar}）
    //* <li>SQLException              SQL异常
    //* </ul>
    public static <T> @NotNull T getObject(@NotNull ResultSet rs, @NotNull Class<T> type, boolean ignoreException) {
        try {
            ResultSetMetaData md = rs.getMetaData();
            int columnCount = md.getColumnCount();
            TreeMap<String, Object> data = new TreeMap<>();
            for (int i = 1; i <= columnCount; i++)
                data.put(md.getColumnName(i).toLowerCase(Locale.ROOT), rs.getObject(i));
            return getObject(data, type, ignoreException);
        } catch (RuntimeException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * 将一个ResultSet实例化为一个对象，该方法对成员变量<b>忽略大小写</b>，请注意
     *
     * @param data            输入的ResultSet对象，该方法不会使用 {@link ResultSet#next()}
     * @param type            输出对象类型
     * @param <T>             输出对象类型
     * @param ignoreException 是否忽略异常，但无法忽略构造对象时的异常
     * @return 输出对象
     * @throws RuntimeException 解析中出现了异常，详见Print出的错误信息
     */
    public static <T> @NotNull T getObject(Map<String, Object> data, Class<T> type, boolean ignoreException) {
        try {

            Constructor<T> c = type.getDeclaredConstructor();
            c.setAccessible(true);
            T output = c.newInstance();
            for (Field field : type.getDeclaredFields()) {
                String fieldName = field.getName().toLowerCase(Locale.ROOT);
                FieldProperty[] property = field.getAnnotationsByType(FieldProperty.class);
                if (property.length > 0 && !property[0].rawName().equals("")) fieldName = property[0].rawName();
                field.setAccessible(true);
                if (data.containsKey(fieldName)) {
                    try {
                        field.set(output, data.get(fieldName));
                    } catch (IllegalArgumentException ex) {
                        if (output instanceof ResultSetTypeConverter) {
                            try {
                                field.set(output, ((ResultSetTypeConverter) output).converterType(data.get(fieldName), field.getType(), fieldName));
                            } catch (IllegalArgumentException | IllegalAccessException ex2) {
                                if (!ignoreException) throw ex2;
                            }
                        } else if (!ignoreException) throw ex;
                    }
                }
            }
            return output;
        } catch (Exception ex) {
            throw new RuntimeException();
        }
    }

    @NotNull
    @UnmodifiableView
    //@SuppressWarnings("rawtypes")
    public static <T> Map<String, FiledData> getFiledListFromObject(@NotNull T object) {
        HashMap<String, FiledData> out = new HashMap<>();
        for (Field field : object.getClass().getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())) continue;
            FieldProperty property = null;
            FieldProperty[] properties = field.getAnnotationsByType(FieldProperty.class);
            if (properties.length > 0) property = properties[0];
            try {
                out.put(field.getName(), new FiledData<>(property, field.get(object), field.getType()));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return Collections.unmodifiableMap(out);
    }

    @NotNull
    @UnmodifiableView
    public static <T> Map<String, FieldProperty> getFieldProperties(Class<T> type) {
        HashMap<String, FieldProperty> out = new HashMap<>();
        for (Field field : type.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())) continue;
            FieldProperty property = null;
            FieldProperty[] properties = field.getAnnotationsByType(FieldProperty.class);
            if (properties.length > 0) property = properties[0];
            out.put(field.getName(), property);
        }
        return Collections.unmodifiableMap(out);
    }

    public static <T> boolean setFieldData(T object, String fieldName, Object data) {
        try {
            Field field = object.getClass().getField(fieldName);
            field.set(object, data);
        } catch (Exception ex) {
            logger.warn(ex);
            return false;
        }
        return true;
    }

    public static <T, V> @Nullable V getFieldData(T object, String fieldName) {
        try {
            Field field = object.getClass().getField(fieldName);
            return (V) field.get(object);
        } catch (Exception ex) {
            logger.warn(ex);
            return null;
        }
    }

    public static class FiledData<T> implements Serializable {
        public final FieldProperty property;
        public final Object data;
        public final Class<T> type;

        public FiledData(FieldProperty property, Object data, Class<T> clazz) {
            this.property = property;
            this.data = data;
            this.type = clazz;
        }
    }
}
